Collecting censusgeocode
  Downloading https://files.pythonhosted.org/packages/86/6f/84434d2f5e382dd224b6f1035cb78a718ce31a9c2125803ac03a94357d6b/censusgeocode-0.4.4-py2.py3-none-any.whl
Requirement already satisfied: requests[security]<3,>=2.18 in /usr/local/lib/python3.6/dist-packages (from censusgeocode) (2.23.0)
Collecting requests-toolbelt<1,>=0.8.0
  Downloading https://files.pythonhosted.org/packages/60/ef/7681134338fc097acef8d9b2f8abe0458e4d87559c689a8c306d0957ece5/requests_toolbelt-0.9.1-py2.py3-none-any.whl (54kB)
     |████████████████████████████████| 61kB 5.3MB/s 
Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from censusgeocode) (1.15.0)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.6/dist-packages (from requests[security]<3,>=2.18->censusgeocode) (2020.12.5)
Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.6/dist-packages (from requests[security]<3,>=2.18->censusgeocode) (3.0.4)
Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.6/dist-packages (from requests[security]<3,>=2.18->censusgeocode) (1.24.3)
Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.6/dist-packages (from requests[security]<3,>=2.18->censusgeocode) (2.10)
Collecting cryptography>=1.3.4; extra == "security"
  Downloading https://files.pythonhosted.org/packages/c9/de/7054df0620b5411ba45480f0261e1fb66a53f3db31b28e3aa52c026e72d9/cryptography-3.3.1-cp36-abi3-manylinux2010_x86_64.whl (2.6MB)
     |████████████████████████████████| 2.6MB 14.5MB/s 
Collecting pyOpenSSL>=0.14; extra == "security"
  Downloading https://files.pythonhosted.org/packages/b2/5e/06351ede29fd4899782ad335c2e02f1f862a887c20a3541f17c3fa1a3525/pyOpenSSL-20.0.1-py2.py3-none-any.whl (54kB)
     |████████████████████████████████| 61kB 6.7MB/s 
Requirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.6/dist-packages (from cryptography>=1.3.4; extra == "security"->requests[security]<3,>=2.18->censusgeocode) (1.14.4)
Requirement already satisfied: pycparser in /usr/local/lib/python3.6/dist-packages (from cffi>=1.12->cryptography>=1.3.4; extra == "security"->requests[security]<3,>=2.18->censusgeocode) (2.20)
Installing collected packages: requests-toolbelt, censusgeocode, cryptography, pyOpenSSL
Successfully installed censusgeocode-0.4.4 cryptography-3.3.1 pyOpenSSL-20.0.1 requests-toolbelt-0.9.1
Collecting sodapy
  Downloading https://files.pythonhosted.org/packages/9e/74/95fb7d45bbe7f1de43caac45d7dd4807ef1e15881564a00eef489a3bb5c6/sodapy-2.1.0-py2.py3-none-any.whl
Requirement already satisfied: requests>=2.20.0 in /usr/local/lib/python3.6/dist-packages (from sodapy) (2.23.0)
Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.6/dist-packages (from requests>=2.20.0->sodapy) (1.24.3)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.6/dist-packages (from requests>=2.20.0->sodapy) (2020.12.5)
Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.6/dist-packages (from requests>=2.20.0->sodapy) (2.10)
Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.6/dist-packages (from requests>=2.20.0->sodapy) (3.0.4)
Installing collected packages: sodapy
Successfully installed sodapy-2.1.0
Collecting geopandas
  Downloading https://files.pythonhosted.org/packages/f7/a4/e66aafbefcbb717813bf3a355c8c4fc3ed04ea1dd7feb2920f2f4f868921/geopandas-0.8.1-py2.py3-none-any.whl (962kB)
     |████████████████████████████████| 972kB 11.4MB/s 
Requirement already satisfied: shapely in /usr/local/lib/python3.6/dist-packages (from geopandas) (1.7.1)
Collecting pyproj>=2.2.0
  Downloading https://files.pythonhosted.org/packages/e4/ab/280e80a67cfc109d15428c0ec56391fc03a65857b7727cf4e6e6f99a4204/pyproj-3.0.0.post1-cp36-cp36m-manylinux2010_x86_64.whl (6.4MB)
     |████████████████████████████████| 6.5MB 39.7MB/s 
Collecting fiona
  Downloading https://files.pythonhosted.org/packages/37/94/4910fd55246c1d963727b03885ead6ef1cd3748a465f7b0239ab25dfc9a3/Fiona-1.8.18-cp36-cp36m-manylinux1_x86_64.whl (14.8MB)
     |████████████████████████████████| 14.8MB 337kB/s 
Requirement already satisfied: pandas>=0.23.0 in /usr/local/lib/python3.6/dist-packages (from geopandas) (1.1.5)
Requirement already satisfied: certifi in /usr/local/lib/python3.6/dist-packages (from pyproj>=2.2.0->geopandas) (2020.12.5)
Requirement already satisfied: six>=1.7 in /usr/local/lib/python3.6/dist-packages (from fiona->geopandas) (1.15.0)
Collecting click-plugins>=1.0
  Downloading https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl
Requirement already satisfied: attrs>=17 in /usr/local/lib/python3.6/dist-packages (from fiona->geopandas) (20.3.0)
Requirement already satisfied: click<8,>=4.0 in /usr/local/lib/python3.6/dist-packages (from fiona->geopandas) (7.1.2)
Collecting munch
  Downloading https://files.pythonhosted.org/packages/cc/ab/85d8da5c9a45e072301beb37ad7f833cd344e04c817d97e0cc75681d248f/munch-2.5.0-py2.py3-none-any.whl
Collecting cligj>=0.5
  Downloading https://files.pythonhosted.org/packages/42/1e/947eadf10d6804bf276eb8a038bd5307996dceaaa41cfd21b7a15ec62f5d/cligj-0.7.1-py3-none-any.whl
Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.6/dist-packages (from pandas>=0.23.0->geopandas) (2018.9)
Requirement already satisfied: numpy>=1.15.4 in /usr/local/lib/python3.6/dist-packages (from pandas>=0.23.0->geopandas) (1.19.4)
Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.6/dist-packages (from pandas>=0.23.0->geopandas) (2.8.1)
Installing collected packages: pyproj, click-plugins, munch, cligj, fiona, geopandas
Successfully installed click-plugins-1.1.1 cligj-0.7.1 fiona-1.8.18 geopandas-0.8.1 munch-2.5.0 pyproj-3.0.0.post1
Requirement already satisfied: nbconvert in /usr/local/lib/python3.6/dist-packages (5.6.1)
Requirement already satisfied: pandocfilters>=1.4.1 in /usr/local/lib/python3.6/dist-packages (from nbconvert) (1.4.3)
Requirement already satisfied: entrypoints>=0.2.2 in /usr/local/lib/python3.6/dist-packages (from nbconvert) (0.3)
Requirement already satisfied: mistune<2,>=0.8.1 in /usr/local/lib/python3.6/dist-packages (from nbconvert) (0.8.4)
Requirement already satisfied: pygments in /usr/local/lib/python3.6/dist-packages (from nbconvert) (2.6.1)
Requirement already satisfied: nbformat>=4.4 in /usr/local/lib/python3.6/dist-packages (from nbconvert) (5.0.8)
Requirement already satisfied: jinja2>=2.4 in /usr/local/lib/python3.6/dist-packages (from nbconvert) (2.11.2)
Requirement already satisfied: traitlets>=4.2 in /usr/local/lib/python3.6/dist-packages (from nbconvert) (4.3.3)
Requirement already satisfied: jupyter-core in /usr/local/lib/python3.6/dist-packages (from nbconvert) (4.7.0)
Requirement already satisfied: testpath in /usr/local/lib/python3.6/dist-packages (from nbconvert) (0.4.4)
Requirement already satisfied: defusedxml in /usr/local/lib/python3.6/dist-packages (from nbconvert) (0.6.0)
Requirement already satisfied: bleach in /usr/local/lib/python3.6/dist-packages (from nbconvert) (3.2.1)
Requirement already satisfied: jsonschema!=2.5.0,>=2.4 in /usr/local/lib/python3.6/dist-packages (from nbformat>=4.4->nbconvert) (2.6.0)
Requirement already satisfied: ipython-genutils in /usr/local/lib/python3.6/dist-packages (from nbformat>=4.4->nbconvert) (0.2.0)
Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.6/dist-packages (from jinja2>=2.4->nbconvert) (1.1.1)
Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from traitlets>=4.2->nbconvert) (1.15.0)
Requirement already satisfied: decorator in /usr/local/lib/python3.6/dist-packages (from traitlets>=4.2->nbconvert) (4.4.2)
Requirement already satisfied: packaging in /usr/local/lib/python3.6/dist-packages (from bleach->nbconvert) (20.8)
Requirement already satisfied: webencodings in /usr/local/lib/python3.6/dist-packages (from bleach->nbconvert) (0.5.1)
Requirement already satisfied: pyparsing>=2.0.2 in /usr/local/lib/python3.6/dist-packages (from packaging->bleach->nbconvert) (2.4.7)

Grab and clean ISR data

Below, I grab Investigatory Stop Report (ISR) data from the Chicago Police Department's website which is available for stops over time periods January 1, 2016 to January 16, 2018 and January 1, 2018 to December 31, 2018. I then deduplicate records and do some data type conversions to prepare for the visualizations.

Note: I load data from the web and clean all within this file.

Mounted at /content/gdrive
DataTransformerRegistry.enable('default')
/usr/local/lib/python3.6/dist-packages/IPython/core/interactiveshell.py:2718: DtypeWarning: Columns (0,4,11,12,13,14,16,18,19,22,23,29,35,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,59,63,64,65,72,74,75,99,100,101,102,103,104,109,110,111,120,121,132,134,138,139,140,141,142,143,144,145,146,147,148,149,150,152,153,154,155,156,157,159,161,163,166,169) have mixed types.Specify dtype option on import or set low_memory=False.
  interactivity=interactivity, compiler=compiler, result=result)
/usr/local/lib/python3.6/dist-packages/IPython/core/interactiveshell.py:2718: DtypeWarning: Columns (0,4,11,13,14,16,18,19,22,23,29,35,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,54,59,63,64,65,66,72,74,75,100,102,103,104,110,111,117,119,120,121,132,134,138,139,140,141,142,143,144,146,148,150,152,153,154,155,156,157,159,161,163,164,166,169) have mixed types.Specify dtype option on import or set low_memory=False.
  interactivity=interactivity, compiler=compiler, result=result)
/usr/local/lib/python3.6/dist-packages/IPython/core/interactiveshell.py:2718: DtypeWarning: Columns (0,4,10,12,13,15,17,18,21,22,28,34,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,53,58,62,63,64,71,73,74,99,101,102,103,109,110,116,118,119,120,121,122,131,133,137,138,139,140,141,142,143,145,146,147,148,149,151,152,153,154,155,156,158,160,162,165,168) have mixed types.Specify dtype option on import or set low_memory=False.
  interactivity=interactivity, compiler=compiler, result=result)
/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:29: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:30: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

Set themes

ThemeRegistry.enable('krista_theme')

Time Trends

I begin with a basic line chart that demonstrates the total ISRs by month over time. Seasonal dips in the number of stops per month occur towards the end of fall and tend to increase again in late winter/early spring. There is a noticeable increase in the volume of stops during the high season of 2018 as compared to the prior two years. In later charts, I continue to examine trends over time to see if there are patterns which may explain this increase.

District Trends

After observing how stops have fluctuated over time, I want to examine whether these trends demonstrate any differences by police district. Below, it is apparent that District 7 likely made up the lion's share of the spike in ISRs in mid-2016. The subsequent spike in stops in mid-2018 occurred in multiple police districts but appears particularly strong in districts 11 and 7.

Mapping ISRs by District

In the city of Chicago, district 11 is located on the west side and primarily encompasses West Garfield Park, a neighborhood notorious for high rates of homicide. District 7 primarily encompasses Englewood on the south side. The populations in both districts are predominantly African American. Below, I map ISRs in the year 2018 by police district to provide geographical context.

Note: I have every intention of mapping the specific location of stops but I only just got access to a Midway account at RCC for geocoding.

Grab district boundaries and clean ISR data

/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:2: UserWarning: Geometry is in a geographic CRS. Results from 'centroid' are likely incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.

  
/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:3: UserWarning: Geometry is in a geographic CRS. Results from 'centroid' are likely incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.

  This is separate from the ipykernel package so we can avoid doing imports until
/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:4: UserWarning: Geometry is in a geographic CRS. Results from 'centroid' are likely incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.

  after removing the cwd from sys.path.

Visual

Stops by Race

Racially discriminatory policing is a pernicious issue in police departments across the country and Chicago is no exception. In the stack area chart below, I used color and area to visualize how the stops have varied by race over time. CPD has consistently stopped Black/African American individuals at the highest rate and by a wide margin, followed by Hispanic.

Note: Black and White Hispanic are combined into "Hispanic". "Other, Unknown" includes Asian/Pacific Islanders and American Indian/Alaska Natives.

Pat Downs and Searches

The ISR data provided by the city of Chicago includes an indicator for whether or not the stop included a pat down and whether or not those pat downs included a search. Below, I explore how pat downs and searches have varied over time. We see that pat downs and searches have varied in conjunction with ISRs overall over time. However, most of the rise in ISRs in 2018 were driven by ISRs that did not involve pat downs or searches.

Pat Downs and Searches by Race

Given findings from the previous two visualizations, one might wonder if the relative likelihood of being patted down or searched after a stop differs by race. I have found that from 2016-2018, the chances of a Black/African American or Hispanic subjects being patted down or searched after a stop has hovered at close to 5%. However, for white subjects, the chances of being patted down or searched after a stop has not exceeded 4% since the early months of 2016.

Use of Body Cameras

Many policy advocates have touted the use of body cameras as a mechanism to prevent police brutality. Below, I explore the volume of ISRs that have been recorded by a body camera. The use of body cameras has noticeable increased over the past couple years. The rate of adoption has varied by district but increased across all of them.

The adoption of body cameras has not resulted in a reduction of the volume of stops overall and stops have continued to disproportionately target people of color, as seen in previous charts.

Mapping Use of Body Cameras

Combine with districts

Visual

Grab and clean crime data

The next topic I will explore is the relationship between ISRs and crime. To do so, I take data from the city of Chicago's data portal.

WARNING:root:Requests made without an app_token will be subject to strict throttling limits.

Time Trends in ISRs and Crime by District

Below, I repeat the monthly line chart that I created for ISRs above, but I add in a new line demonstrating crimes and distinguish the two using color. There is some correlation between the volume of ISRs and crimes over time. However, throughout the year, it seems ISRs begin pre-emptively increase around December/January whereas crime does not begin its uptick until later in the year in February/March.

Combine crime and ISR data

Visual

Crimes and ISRs by district

Below, I replicate the line chart above but this time I facet each chart by district. In this way, I use a text encoding to denote which chart represents each district. Here, we see some disparities and variation over time and by district with regards to the relationship between crimes and ISRs. For example, in district 11, ISRs suddenly drop in 2017 without a corresponding drop in crime. In district 1, ISRs suddenly increase in 2018 without a corresponding increas in crime. Districts 17 and 18 appear to have relatively similar levels of ISRs over time despite district 18 having consistently higher levels of crime.

Combine crime and ISR data by district

Visual

Mapping Crimes and ISRs

Below, I map police districts to provide visual context for where there is a discrepancy between crimes and ISRs. In 2018, crimes were highly concentrated in the loop and the Near North Side neighborhoods which roughly corresponds to police districts 1 and 18. We see in the map below that all lakefront districts near downtown had above average crime rates but below average ISRs compared to the rest of the city.

Data Sources

*Chicago Police Department's Investigatory Stop Reports data can be found at https://home.chicagopolice.org/statistics-data/isr-data/ and https://home.chicagopolice.org/isr-data-2018/
^The crimes dataset on Chicago's Data Portal can be found at https://data.cityofchicago.org/Public-Safety/Crimes-2001-to-present/ijzp-q8t2/data